import SSH2Promise from 'ssh2-promise'
import SSHConfig from 'ssh2-promise/lib/sshConfig'
import stripAnsi from 'strip-ansi'
import splitLines from 'split-lines'
import { SSHResultType } from '../types'
import { SSHUtil } from '../utils'

export const SSH_Name = 'SSH'

export class SSH {
  // 服务器连接配置
  private readonly config: SSHConfig
  // 服务器连接实例
  private ssh: SSH2Promise | null = null
  // 是否已连接服务器
  private connected: boolean = false
  // 与服务器 Shell 交互的 socket 实例
  private socket: any = null
  // 是否已经与服务器创建会话
  private isSession: boolean = false
  // 当前执行的命令
  private currentCmd: Record<string, string> = {}

  constructor(config: SSHConfig) {
    this.config = config
  }

  /**
   * 是否已经 SSH 连接到服务器
   *
   * @returns {boolean} true 已连接，false 未连接
   */
  get isConnected(): boolean {
    return this.connected
  }

  /**
   * SSH 连接服务器
   *
   * @returns {Promise<SSHResultType>} SSH 连接结果
   */
  async connect(): Promise<SSHResultType> {
    if (this.connected)
      return SSHUtil.genSuccessResultByConf(this.config, { message: '已连接成功' })
    try {
      this.ssh = new SSH2Promise(this.config)
      await this.ssh.connect()
      this.connected = true
      return SSHUtil.genSuccessResultByConf(this.config, { message: '连接成功' })
    } catch (err: any) {
      this.connected = false
      this.ssh = null
      return SSHUtil.genFailResultByConf(this.config, {
        message: err.message ?? '连接失败',
        detail: err
      })
    }
  }

  /**
   * 断开与服务器的 SSH 连接
   *
   * @returns {Promise<SSHResultType>} 断开与服务器的 SSH 连接的结果
   */
  async close(): Promise<SSHResultType> {
    if (!this.ssh || !this.connected)
      SSHUtil.genSuccessResultByConf(this.config, { message: '已断开服务器连接' })
    try {
      await this.ssh?.close()
      this.connected = false
      this.ssh = null
      return SSHUtil.genSuccessResultByConf(this.config, { message: '断开服务器连接成功' })
    } catch (err: any) {
      return SSHUtil.genFailResultByConf(this.config, {
        message: err.message ?? '断开服务器连接失败',
        detail: err
      })
    }
  }

  /**
   * SSH 连接服务器后执行命令<br/>
   * 注意：如果未连接服务器，会返回失败结果，所以需要先调用 {@link connect} 方法连接服务器
   *
   * @param {string} cmd 需要执行的命令
   * @returns {Promise<SSHResultType>} 执行命令结果
   */
  async exec(cmd: string): Promise<SSHResultType> {
    if (!this.ssh || !this.connected)
      return SSHUtil.genFailResultByConf(this.config, { message: '未连接服务器，请先连接服务器' })
    try {
      const res = await this.ssh.exec(cmd)
      return SSHUtil.genSuccessResultByConf(this.config, {
        message: `${cmd} 执行成功`,
        detail: res
      })
    } catch (err: any) {
      return SSHUtil.genFailResultByConf(this.config, {
        message: err.message ?? `${cmd} 执行失败`,
        detail: err
      })
    }
  }

  /**
   * 是否已经与服务器创建会话
   *
   * @returns {boolean} true 已创建会话，false 未创建会话
   */
  get isSessionOpen(): boolean {
    return this.isSession
  }

  /**
   * 与服务器创建 Shell 会话
   *
   * @param {(data: string) => void} onData 服务器返回数据时的回调函数
   * @param {() => void} onClose 与服务器之间的 Shell 会话关闭时的回调函数
   * @returns {Promise<SSHResultType>} 创建会话结果
   */
  async sessionOpen(onData: (data: string) => void, onClose: () => void): Promise<SSHResultType> {
    if (!this.ssh || !this.connected)
      return SSHUtil.genFailResultByConf(this.config, { message: '未连接服务器，请先连接服务器' })
    try {
      this.socket = await this.ssh.shell()
      // 监听服务器返回数据
      this.socket.on('data', (data: Buffer) => {
        // 最终要返回的数据
        let dataStr = ''
        // 由于服务器可能会一次性返回多行数据，所以分行判断排除与当前执行的命令相同的数据
        const dataLines = splitLines(data.toString('utf8'))
        for (const dataLine of dataLines) {
          // 如果服务器返回的当前行数据与当前执行的命令相同，则将该数据排除
          if (Reflect.has(this.currentCmd, dataLine.trim())) {
            Reflect.deleteProperty(this.currentCmd, dataLine.trim())
          } else if (Reflect.has(this.currentCmd, stripAnsi(dataLine).trim())) {
            Reflect.deleteProperty(this.currentCmd, stripAnsi(dataLine).trim())
          } else {
            dataStr += dataLine + '\n'
          }
        }
        // 如果最终要返回的数据为空，直接退出函数
        if (!dataStr.trim()) return
        onData(dataStr)
      })
      // 监听会话关闭
      this.socket.on('close', onClose)
      this.isSession = true
      return SSHUtil.genSuccessResultByConf(this.config, { message: '会话创建成功' })
    } catch (err: any) {
      this.isSession = false
      this.socket = null
      return SSHUtil.genFailResultByConf(this.config, {
        message: err.message ?? '会话创建失败',
        detail: err
      })
    }
  }

  /**
   * 在与服务器创建的 Shell 会话中执行命令
   *
   * @param {string} cmd 需要执行的命令
   * @returns {Promise<SSHResultType>} 命令执行结果
   */
  async sessionExec(cmd: string): Promise<SSHResultType> {
    if (!this.socket || !this.isSession)
      return SSHUtil.genFailResultByConf(this.config, {
        message: '未与服务器创建会话，请先创建会话'
      })
    try {
      this.currentCmd[cmd] = cmd
      this.socket.write(cmd + '\n')
      return SSHUtil.genSuccessResultByConf(this.config, { message: `${cmd} 执行成功` })
    } catch (err: any) {
      return SSHUtil.genFailResultByConf(this.config, {
        message: err.message ?? `${cmd} 执行失败`,
        detail: err
      })
    }
  }

  /**
   * 关闭与服务器创建的 Shell 会话
   *
   * @returns {Promise<SSHResultType>} 关闭会话结果
   */
  async sessionClose(): Promise<SSHResultType> {
    if (!this.socket || !this.isSession)
      return SSHUtil.genSuccessResultByConf(this.config, { message: '会话已关闭' })
    try {
      await this.socket.destroy()
      this.currentCmd = {}
      this.isSession = false
      this.socket = null
      return SSHUtil.genSuccessResultByConf(this.config, { message: '会话关闭成功' })
    } catch (err: any) {
      return SSHUtil.genFailResultByConf(this.config, {
        message: err.message ?? '会话关闭失败',
        detail: err
      })
    }
  }
}
